﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Graphics;
using Arcadium.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.GamerServices;
using Arcadium.Screens.Menus.MenuHelpers;
using Arcadium.Audio;

namespace Arcadium.Screens.Menus.Sidebar
{
    class MenuSidebar : GameScreen
    {
        private enum AsyncOperation
        {
            Create,
            Join,
            JoinInvited
        }

        #region Logic Field

        // The current running networksession
        private NetworkSession networkSession;
        // The networksession will be created asynchronusly, and meanwhile the sidebar will display a loadinganimation
        private IAsyncResult asyncResult;

        private SidebarDropdownList<NetworkSessionType> networkTypeDropdown;
        private SidebarComponent currentComponent;

        // The networktypes available
        //private List<NetworkSessionType> availableSessionTypes;
        //private int selectedSessionType = 0;

        #endregion

        #region View Fields

        // The rendertarget to draw all components
        private RenderTarget2D renderTarget;

        // Textures
        private Texture2D backgroundTexture;
        
        private Texture2D loadingTexture;
        private Vector2 loadingTextureOrigin;
        private Color[] loadingTextureColors;
        private float loadingTextureRotation = 0;

        private SidebarDropdownListView<NetworkSessionType> networkTypeDropdownView;
        private SidebarMenuEntry findSessionMenuEntry;
        private SidebarPlayerList playerList;
        
        // Constants
        private const float sidebarIntent = 90;

        private const float loadingTextureHubOffset = 72;
        private const float loadingTextureYPos = 264;
        private const float loadingTextureRotationSpeed = 15;

        private const float findSessionYPos = 150;

        private const float playerListYPos = 528;
        private const float playerListItemHeight = 138;

        #endregion

        #region Properties


        /// <summary>
        /// Gets the loaded state of the menusidebar.
        /// </summary>
        public bool IsLoaded
        {
            get { return networkSession != null; }
        }


        /// <summary>
        /// The networksession.
        /// </summary>
        public NetworkSession NetworkSession
        {
            get { return networkSession; }
        }


        public bool HasFocus
        {
            get { return hasFocus; }
        }

        private bool hasFocus = false;

        #endregion

        #region Initialization

        /// <summary>
        /// Creates a menu sidebar and begins an asynchrounous operation to create a networksession
        /// </summary>
        /// <param name="availableSessionTypes"></param>
        /// <param name="localGamers"></param>
        public MenuSidebar(List<NetworkSessionType> availableSessionTypes, PlayerIndex playerIndex) : this()
        {
            networkTypeDropdown = new SidebarDropdownList<NetworkSessionType>(availableSessionTypes);
            // Begin an asynchronous create network session operation.
            this.asyncResult = NetworkSession.BeginCreate(networkTypeDropdown.SelectedItem,
                NetworkSessionComponent.ChooseGamers(networkTypeDropdown.SelectedItem, playerIndex),
                                                NetworkSessionComponent.MaxGamers, 0, null, null, AsyncOperation.Create);
        }

        /// <summary>
        /// For joining other sessions
        /// </summary>
        /// <param name="networkSesion"></param>
        public MenuSidebar(NetworkSession networkSesion) : this()
        {
            LoadSession(networkSesion);
        }

        private MenuSidebar()
        {
            IsPopup = true;

            TransitionOnTime = TimeSpan.FromSeconds(.25);
            TransitionOffTime = TimeSpan.FromSeconds(.25);
        }

        /// <summary>
        /// Loads content.
        /// </summary>
        public override void LoadContent()
        {
            base.LoadContent();

            ContentManager content = ScreenManager.Game.Content;

            backgroundTexture = content.Load<Texture2D>("MenuSidebar\\background");
            renderTarget = new RenderTarget2D(ScreenManager.Game.GraphicsDevice, 
                backgroundTexture.Width, backgroundTexture.Height);

            loadingTexture = content.Load<Texture2D>("MenuSidebar\\loadingNew");
            loadingTextureOrigin = new Vector2(loadingTexture.Width,
                loadingTexture.Height) / 2;
            loadingTextureColors = new Color[]{ 
                Color.Black,
                new Color(20, 20, 20),
                new Color(38, 38, 38),
                new Color(56, 56, 56),
                new Color(74, 74, 74),
                new Color(92, 92, 92),
                Color.DimGray,
                Color.Gray
            };

            // Networksession-type dropdownlist

            networkTypeDropdown = new SidebarDropdownList<NetworkSessionType>(networkTypeDropdown.Items);
            networkTypeDropdown.Visible = networkTypeDropdown.Items != null;

            networkTypeDropdownView = new SidebarDropdownListView<NetworkSessionType>(networkTypeDropdown,
                new Vector2(sidebarIntent, 0));
            networkTypeDropdownView.LoadContent(content);
            
            // Find session button
            findSessionMenuEntry = new SidebarMenuEntry("Find Session",
                    new Vector2(sidebarIntent + ViewResource.TextIndent, findSessionYPos));
            findSessionMenuEntry.Visible = networkTypeDropdown.Items != null || 
                networkTypeDropdown.SelectedItem == NetworkSessionType.Local;
            
            // List of players
            playerList = new SidebarPlayerList(new Vector2(sidebarIntent + ViewResource.TextIndent, playerListYPos), 
                playerListItemHeight);
            
            networkTypeDropdown.ItemSelected += NewNetworkSelected;
            findSessionMenuEntry.Selected += FindSessionsMenuEntrySelected;
            networkTypeDropdown.IsSelected = true;

            networkTypeDropdown.NextComponent = findSessionMenuEntry;
            findSessionMenuEntry.PreviousComponent = networkTypeDropdown;
            //findSessionMenuEntry.NextComponent = playerList;
            //playerList.PreviousComponent = findSessionMenuEntry;

            currentComponent = networkTypeDropdown;
        }

        /// <summary>
        /// Loads a session.
        /// </summary>
        public void LoadSession(NetworkSession networkSession)
        {
            this.asyncResult = null;
            this.networkSession = networkSession;
            this.networkSession.AllowJoinInProgress = true;
            // Create a component that will manage the session we just created.
            NetworkSessionComponent.Create(ScreenManager, networkSession);
            playerList.Load(networkSession.AllGamers);
        }

        /// <summary>
        /// Ends everything and goes back to ActivationScreen
        /// </summary>
        public void ExitSession()
        {
            NetworkSessionComponent.LeaveSessionConfirm(ScreenManager.Game, ControllingPlayer.Value);
        }

        /// <summary>
        /// Ends this session to give place for another session.
        /// </summary>
        public void DisposeSession()
        {
            NetworkSessionComponent.DisposeSession(ScreenManager.Game);
            networkSession = null;
        }

        #endregion

        #region Handle Input

        public override void HandleInput(InputState input)
        {
            PlayerIndex playerIndex;

            if (input.IsMenuRight(ControllingPlayer))
            {
                AudioManager.MenuForward.Play();
                hasFocus = true;
            }

            // The sidebar itself has focus
            if (hasFocus && IsLoaded)
            {
                // If focus is locked to the current component (such as the networkdropdown)
                if (currentComponent.LockedFocus)
                {
                    currentComponent.HandleInput(ControllingPlayer, input);
                }
                else
                {
                    // Give access to current screen
                    if (input.IsMenuLeft(ControllingPlayer))
                    {
                        hasFocus = false;
                    }

                    // Cycle through sidebar entries
                    if (input.IsMenuUp(ControllingPlayer))
                    {
                        AudioManager.MenuChangeItem.Play();
                        currentComponent.IsSelected = false;
                        while (currentComponent.PreviousComponent != null &&
                            !(currentComponent = currentComponent.PreviousComponent).Visible) ;
                        currentComponent.IsSelected = true;
                    }
                    if (input.IsMenuDown(ControllingPlayer))
                    {
                        AudioManager.MenuChangeItem.Play();
                        currentComponent.IsSelected = false;
                        while (currentComponent.NextComponent != null &&
                            !(currentComponent = currentComponent.NextComponent).Visible) ;
                        currentComponent.IsSelected = true;
                    }
                }

                if (input.IsMenuSelect(ControllingPlayer, out playerIndex))
                {
                    AudioManager.MenuForward.Play();
                    currentComponent.Select();
                }

                if (input.IsMenuCancel(ControllingPlayer, out playerIndex))
                {
                    if (currentComponent.LockedFocus)
                    {
                        AudioManager.MenuBack.Play();
                        currentComponent.Deselect();
                    }
                    else
                    {
                        hasFocus = false;
                    }
                }

                if (!hasFocus)
                    AudioManager.MenuForward.Play();
            }
        }

        #endregion

        #region Update and Draw

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, false);

            // Has our asynchronous operation completed?
            if (!IsLoaded && asyncResult != null && asyncResult.IsCompleted)
            {
                // Done loading
                try
                {
                    if ((AsyncOperation) asyncResult.AsyncState == AsyncOperation.Create)
                    {
                        LoadSession(NetworkSession.EndCreate(asyncResult));
                    }
                    else if ((AsyncOperation)asyncResult.AsyncState == AsyncOperation.Join)
                    {
                        LoadSession(NetworkSession.EndJoin(asyncResult));
                    }
                    else if ((AsyncOperation) asyncResult.AsyncState == AsyncOperation.JoinInvited)
                    {
                        // TODO: Sort out this as well as the methods at the bottom
                        //JoinInvitedOperationCompleted(asyncResult, ScreenManager, someguy);
                    }

                    
                }
                catch (Exception exception)
                {
                    NetworkErrorScreen errorScreen = new NetworkErrorScreen(exception);
                    //ScreenManager.AddScreen(errorScreen, ControllingPlayer);
                    // TODO: Error handeling, if we fail to start a session, we might need to go back to start-menu
                    LoadingScreen.Load(ScreenManager, false, null, new BackgroundScreen(), new ActivationScreen());

                }
            }

            if (IsLoaded)
            {
                if (networkSession.SessionState == NetworkSessionState.Playing)
                {
                    // TODO: There's the signal, start a game... hmm I wonder which game...
                    LoadingScreen.Load(ScreenManager, true, null,
                                           new GameplayScreen(networkSession));
                }
            }
            else
            {
                loadingTextureRotation += loadingTextureRotationSpeed * 
                    (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            
            DrawRenderTarget();
        }

        /// <summary>
        /// Actual drawing of the sidebar and it's subcomponents to it's rendertarget.
        /// </summary>
        private void DrawRenderTarget()
        {
            SpriteBatch spriteBatch = ViewResource.SpriteBatch;
            GraphicsDevice device = spriteBatch.GraphicsDevice;

            device.SetRenderTarget(renderTarget);
            device.Clear(Color.Transparent);

            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

            // Background
            spriteBatch.Draw(backgroundTexture, Vector2.Zero, null, Color.White, 0,
                Vector2.Zero, View.ScreenScale, SpriteEffects.None, 1);

            if (!IsLoaded)
            {
                Vector2 rotationPosition = new Vector2(loadingTextureHubOffset * View.ScreenScale.X, 0);

                Matrix rotationMatrix = Matrix.CreateRotationZ(MathHelper.PiOver4);
                for (float v = 0; v < 8; v++)
                {
                    rotationPosition = Vector2.Transform(rotationPosition, rotationMatrix);
                    spriteBatch.Draw(loadingTexture,
                        View.Adjust(
                        new Vector2((backgroundTexture.Width + sidebarIntent) / 2,
                            loadingTextureYPos) + rotationPosition), null, 
                            loadingTextureColors[(int)(loadingTextureRotation + v) % loadingTextureColors.Length],
                        0, loadingTextureOrigin, View.ScreenScale, SpriteEffects.None, 0);
                }
            }
            else
            {

                // TODO: Fix proper drawing

                //spriteBatch.DrawString(ViewResource.MenuFont, "Yes, this is test",
                //    View.Adjust(new Vector2(sidebarIntent + ViewResource.TextIndent, loadingTextureYPos)), Color.White, 0, Vector2.Zero, 
                //    .2f * View.ScreenScale, SpriteEffects.None, 0);

                // Draw all components
                if (networkTypeDropdown.Items != null)
                {
                    networkTypeDropdownView.Draw(spriteBatch, hasFocus);
                }

                if (NetworkSession.SessionType != NetworkSessionType.Local)
                {
                    findSessionMenuEntry.Draw(spriteBatch, hasFocus);
                }
                playerList.Draw(spriteBatch, hasFocus);
            }

            spriteBatch.End();

            device.SetRenderTarget(null);

        }

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch spriteBatch = ViewResource.SpriteBatch;

            Vector2 offset = new Vector2(backgroundTexture.Width * 
                TransitionPosition * View.ScreenScale.X, 0);

            spriteBatch.Begin();

            spriteBatch.Draw(renderTarget, 
                View.Adjust(new Vector2(View.ScreenWidth - backgroundTexture.Width, 0)) + offset, Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Event handler for when an item in the Dropdown menu is selected.
        /// </summary>
        void NewNetworkSelected(object sender, GenericEventArgs<NetworkSessionType> e)
        {

            // TODO: Remove action block, and verify that it works (new network selected)

            //return;

            findSessionMenuEntry.Visible = (e.Result != NetworkSessionType.Local);
            // Start a new session with the selected networktype
            DisposeSession();
            this.asyncResult = NetworkSession.BeginCreate(e.Result,
            NetworkSessionComponent.ChooseGamers(e.Result, ControllingPlayer.Value),
                                            NetworkSessionComponent.MaxGamers, 0, null, null, AsyncOperation.Create);
        }

        /// <summary>
        /// Event handler for when the Find Sessions menu entry is selected.
        /// </summary>
        void FindSessionsMenuEntrySelected(object sender, EventArgs e)
        {

            // TODO: Remove action block, and verify that it works (find session selected)
            //return;

            try
            {
                // To search for another session, we need to end our own first.
                DisposeSession();

                // Begin an asynchronous find network sessions operation.
                IAsyncResult asyncResult = NetworkSession.BeginFind(networkTypeDropdown.SelectedItem,
                                                        NetworkSessionComponent.ChooseGamers(networkTypeDropdown.SelectedItem,
                                                         ControllingPlayer.Value), null, null, null);

                // Activate the network busy screen, which will display
                // an animation until this operation has completed.
                NetworkBusyScreen busyScreen = new NetworkBusyScreen(asyncResult);

                busyScreen.OperationCompleted += FindSessionsOperationCompleted;

                ScreenManager.AddScreen(busyScreen, ControllingPlayer);
            }
            catch (Exception exception)
            {
                NetworkErrorScreen errorScreen = new NetworkErrorScreen(exception);

                ScreenManager.AddScreen(errorScreen, ControllingPlayer);

                this.asyncResult = NetworkSession.BeginCreate(networkTypeDropdown.SelectedItem,
                NetworkSessionComponent.ChooseGamers(networkTypeDropdown.SelectedItem, ControllingPlayer.Value),
                                                NetworkSessionComponent.MaxGamers, 0, null, null, AsyncOperation.Create);
            }
        }

        /// <summary>
        /// Event handler for when the asynchronous find network sessions
        /// operation has completed.
        /// </summary>
        void FindSessionsOperationCompleted(object sender,
                                            OperationCompletedEventArgs e)
        {
            GameScreen nextScreen = null;

            try
            {
                // End the asynchronous find network sessions operation.
                AvailableNetworkSessionCollection availableSessions =
                                                NetworkSession.EndFind(e.AsyncResult);

                if (availableSessions.Count > 0)
                {
                    // TODO: Handle how we proceed with found sessions
                    // If we did find some sessions, proceed to the JoinSessionScreen.

                    this.asyncResult = NetworkSession.BeginJoin(availableSessions[0], null, AsyncOperation.Join);

                    return;

                    //nextScreen = new JoinSessionScreen(availableSessions);
                }

                // If we didn't find any sessions, display an error.
                availableSessions.Dispose();
                nextScreen = new MessageBoxScreen(Resources.NoSessionsFound, false);

            }
            catch (Exception exception)
            {
                nextScreen = new NetworkErrorScreen(exception);
            }

            this.asyncResult = NetworkSession.BeginCreate(networkTypeDropdown.SelectedItem,
            NetworkSessionComponent.ChooseGamers(networkTypeDropdown.SelectedItem, ControllingPlayer.Value),
                                            NetworkSessionComponent.MaxGamers, 0, null, null, AsyncOperation.Create);

            ScreenManager.AddScreen(nextScreen, ControllingPlayer);
        }

        /// <summary>
        /// Event handler called when the system delivers an invite notification.
        /// This can occur when the user accepts an invite that was sent to them by
        /// a friend (pull mode), or if they choose the "Join Session In Progress"
        /// option in their friends screen (push mode). The handler leaves the
        /// current session (if any), then joins the session referred to by the
        /// invite. It is not necessary to prompt the user before doing this, as
        /// the Guide will already have taken care of the necessary confirmations
        /// before the invite was delivered to you.
        /// </summary>
        public static void InviteAccepted(ScreenManager screenManager,
                                          InviteAcceptedEventArgs e)
        {
            // If we are already in a network session, leave it now.
            NetworkSessionComponent.DisposeSession(screenManager.Game);


            try
            {
                // Which local profiles should we include in this session?
                IEnumerable<SignedInGamer> localGamers =
                    NetworkSessionComponent.ChooseGamers(NetworkSessionType.PlayerMatch, e.Gamer.PlayerIndex);

                // Begin an asynchronous join-from-invite operation.

                if (screenManager.MenuSidebar != null)
                {
                    screenManager.MenuSidebar.asyncResult = 
                        NetworkSession.BeginJoinInvited(localGamers, null, 
                        AsyncOperation.JoinInvited); //new object[] { screenManager, e.Gamer });
                    return;
                }
                IAsyncResult asyncResult = NetworkSession.BeginJoinInvited(
                    localGamers, null, new object[] { screenManager, e.Gamer });

                // Use the loading screen to replace whatever screens were previously
                // active. This will completely reset the screen state, regardless of
                // whether we were in the menus or playing a game when the invite was
                // delivered. When the loading screen finishes, it will activate the
                // network busy screen, which displays an animation as it waits for
                // the join operation to complete.
                NetworkBusyScreen busyScreen = new NetworkBusyScreen(asyncResult);

                busyScreen.OperationCompleted += JoinInvitedOperationCompleted;

                LoadingScreen.Load(screenManager, false, null, busyScreen);
            }
            catch (Exception exception)
            {
                LoadingScreen.Load(screenManager, false, null, 
                    new BackgroundScreen(), new ActivationScreen(),
                    new NetworkErrorScreen(exception));
            }
        }

        static void JoinInvitedOperationCompleted(object sender,
                                                  OperationCompletedEventArgs e)
        {
            JoinInvitedOperationCompleted(e.AsyncResult, 
                (ScreenManager)((object[])e.AsyncResult.AsyncState)[0], 
                (SignedInGamer)((object[])e.AsyncResult.AsyncState)[1]);
        }

        /// <summary>
        /// Event handler for when the asynchronous join-from-invite
        /// operation has completed.
        /// </summary>
        static void JoinInvitedOperationCompleted(IAsyncResult result, ScreenManager screenManager, SignedInGamer gamer)
        {
            try
            {
                // TODO: Clean up and sort out

                if (screenManager.MenuSidebar != null)
                {
                    screenManager.MenuSidebar.LoadSession(NetworkSession.EndJoinInvited(result));
                    return;
                }

                // End the asynchronous join-from-invite operation.
                NetworkSession networkSession =
                                        NetworkSession.EndJoinInvited(result);

                List<NetworkSessionType> availableSessionTypes = new List<NetworkSessionType>() { 
                    NetworkSessionType.PlayerMatch,
                    NetworkSessionType.SystemLink,
                    NetworkSessionType.Local};

                LoadingScreen.Load(screenManager, false, gamer.PlayerIndex, new BackgroundScreen(), new ActivationScreen());
                // Create a sidbar to take care of the session and advance to main menu
                screenManager.AddScreen(new MainMenuScreen(), gamer.PlayerIndex);
                screenManager.AddScreen(new MenuSidebar(networkSession), gamer.PlayerIndex);
            }
            catch (Exception exception)
            {
                LoadingScreen.Load(screenManager, false, null, new BackgroundScreen(), 
                    new ActivationScreen(), new NetworkErrorScreen(exception));
            }
        }


        #endregion
    }
}